#include "syntaxtree.h"
#include "basicparser.h"
#include "parser.h"
#include "tokenizer.h"
#include "utills.h"
#include "errors.h"

namespace CCompiler
{

    SymType *SynAssignOp::getType(SymTable &tbl)
    {
        SymType *left = first_->getType(tbl);
        SymType *right = second_->getType(tbl);
        if(!first_->isLvalue())
            throw Errors::SyntaxError("left of " + binOp_ + " must be lvalue");
        if(binOp_ != "=")
        {
            BinOpOnTypes boot(AssignOp2BinOp[binOp_], BinOpTypePair(left->getSymbolType(), right->getSymbolType()));
            if(!AvailableBinOpsAndHandlers.count(boot))
                throw Errors::SyntaxError("'" + binOp_ + '\'' + " undefined(left=" + left->getName() + ",right=" +
                right->getName() +')');
            right = AvailableBinOpsAndHandlers[boot](first_, second_, tbl);
        }
        BinOpOnTypes boot(binOp_, BinOpTypePair(left->getSymbolType(), right->getSymbolType()));
        if(!AvailableBinOpsAndHandlers.count(boot))
            throw Errors::SyntaxError("'" + binOp_ + '\'' + " undefined(left=" + left->getName() + ",right=" +
            right->getName() +')');
        return AvailableBinOpsAndHandlers[boot](first_, second_, tbl);              
    }

    void SynBinaryOp::print(std::ostream &stream, int ident)
    {
        second_->print(stream, ident + 1);
        makeIDent(stream, ident);
        stream << binOp_ << '\n';
        first_->print(stream, ident + 1);
    }

    void SynUnaryOp::print(std::ostream &stream, int ident)
    {
        if(postfix_)
            first_->print(stream, ident + 1);
        makeIDent(stream, ident);
        stream << unOp_<< '\n';
        if(!postfix_)
            first_->print(stream, ident + 1);
    }

    void SynSizeof::print(std::ostream &stream, int ident)
    {
        makeIDent(stream, ident);
        stream << "sizeof" << '\n';
        type_->print(stream, ident+1);
        stream << '\n';
    }

    void SynFunctionCall::print( std::ostream &stream, int ident /*= 0*/ )
    {
        for(ArgList::reverse_iterator it = arguements_.rbegin(); it != arguements_.rend(); ++it)
            (*it)->print(stream, ident + 1);
        makeIDent(stream, ident);
        stream << "()" << '\n';
        caller_->print(stream, ident + 1);
    }

    void SynVal::print(std::ostream &stream, int ident)
    {
        makeIDent(stream, ident);
        stream << identifier_ << '\n';
    }
    void SynInt::print(std::ostream &stream, int ident)
    {
        makeIDent(stream, ident);
        stream << value_ << '\n';
    }
    void SynChar::print(std::ostream &stream, int ident)
    {
        makeIDent(stream, ident);
        stream << value_ << '\n';
    }
    void SynFloat::print(std::ostream &stream, int ident)
    {
        makeIDent(stream, ident);
        stream << value_ << '\n';
    }
    void SynString::print(std::ostream &stream, int ident)
    {
        makeIDent(stream, ident);
        stream << value_ << '\n';
    }

	std::map<BinOpOnTypes, BinOpHandler> AvailableBinOpsAndHandlers;
	std::map<UnaryOpOnType, UnaryOpHandler> AvailableUnaryOpsAndHandlers;
	/*SymType *HandlerLeaveSame(SynExpression *&a, SynExpression *&b, SymTable &tbl)
	{
		return a->getType(tbl);
	}
    */
    SymType *HandlerUseLeft(SynExpression *&a, SynExpression *&b, SymTable &tbl)
    {
        return a->getType(tbl);
    }
    SymType *HandlerUseLeftWithTypeCheck(SynExpression *&a, SynExpression *&b, SymTable &tbl)
    {
        SymType *at = a->getType(tbl), *bt = b->getType(tbl);
        if(at != bt)
            throw Errors::SyntaxError(at->getName() + " and " + bt->getName() + " are not the same type");
        return a->getType(tbl);
    }
    SymType *HandlerUseLeftWithCast(SynExpression *&a, SynExpression *&b, SymTable &tbl)
    {
        SymType *t = a->getType(tbl);
        b = new SynCast(t, b);
        return t;
    }
    SymType *HandlerUseRight(SynExpression *&a, SynExpression *&b, SymTable &tbl)
    {
        return b->getType(tbl);
    }
    SymType *HandlerUseRightWithCast(SynExpression *&a, SynExpression *&b, SymTable &tbl)
    {
        SymType *t = b->getType(tbl);
        a = new SynCast(t, a);
        return t;
    }
    SymType *HandlerAssignFunctionToPtr(SynExpression *&a, SynExpression *&b, SymTable &tbl)
    {
        SymType *at = a->getType(tbl), *bt = b->getType(tbl), *pt = ((SymFunctionPointer *)at)->getPointed();
        if(bt != pt)
            throw Errors::SyntaxError("Can't assign " + bt->getName() + " to " + at->getName());
        return at;
    }
    SymType *HandlerDistanceBetweenPointers(SynExpression *&a, SynExpression *&b, SymTable &tbl)
    {
        SymType *at = a->getType(tbl), *bt = b->getType(tbl);
        if(at != bt)
            throw Errors::SyntaxError("'-' can't be applied to pointers with different types (left=" + at->getName() +
            ",right=" + bt->getName() + ')');
        return (SymType*)tbl.find("int");
    }
    SymType *HandlerGetFunctionAddress(SynExpression *&a, SymTable &tbl)
    {
        SymFunctionType *t = (SymFunctionType*)a->getType(tbl);
        return getExistingTypeOrInsert(new SymFunctionPointer(t, t->genFuntionTypeName("(*)")), tbl);
    }

    SymType *HandlerDereference(SynExpression *&a, SymTable &tbl)
    {
        return ((SymTypePointer *)a->getType(tbl))->getPointed();
    }
    SymType *HandlerLeaveSame(SynExpression *&a, SymTable &tbl)
    {
        return a->getType(tbl);
    }
    SymType *HandlerGetAddress(SynExpression *&a, SymTable &tbl)
    {
        SymType *t = a->getType(tbl);
        return getExistingTypeOrInsert(new SymTypePointer(t, t->getName()+ "*") , tbl);
    }                                                        

	namespace SyntaxTreeInitializer
	{
		class initializer
		{
			initializer();
		public:
			static initializer initializer_;
		};
		initializer initializer::initializer_;

		void put(std::map<BinOpOnTypes, BinOpHandler> &m, TokenType op,
			SymbolType f, SymbolType s, BinOpHandler hndler)
		{
			m[std::make_pair(StringAnalogOfTokenTypeOperation(op), std::make_pair(f, s))] = hndler;
		}

        void put(std::map<UnaryOpOnType, UnaryOpHandler> &m, TokenType op,
            SymbolType f, UnaryOpHandler hndler)
        {
            m[std::make_pair(StringAnalogOfTokenTypeOperation(op), f)] = hndler;
        }

		initializer::initializer()
		{
			std::map<BinOpOnTypes, BinOpHandler> &bh = AvailableBinOpsAndHandlers;
			using namespace std;
			for(int i = 0; i < lowest_level; ++i)
			{
				for(std::set<TokenType>::iterator it = OpGroups[i].begin();
					it != OpGroups[i].end(); ++it)
				{
					put(bh, *it, stInt, stInt, HandlerUseLeft);
					put(bh, *it, stChar, stChar, HandlerUseLeft);
                    put(bh, *it, stInt, stChar, HandlerUseLeft);
                    put(bh, *it, stChar, stInt, HandlerUseRight);
                    if(!(i >= 2 && i <=4 || i==7 || *it==ttMod))
                    {
                        put(bh, *it, stFloat, stFloat, HandlerUseLeft);
                        put(bh, *it, stFloat, stInt, HandlerUseLeftWithCast);
                        put(bh, *it, stFloat, stChar, HandlerUseLeftWithCast);
                        put(bh, *it, stInt, stFloat, HandlerUseRightWithCast);
                        put(bh, *it, stChar, stFloat, HandlerUseRightWithCast);
                    }
				}
			}
            put(bh, ttPlus, stPointer, stInt, HandlerUseLeft);
            put(bh, ttPlus, stPointer, stChar, HandlerUseLeft);
			put(bh, ttMinus, stPointer, stInt, HandlerUseLeft);
            put(bh, ttMinus, stPointer, stChar, HandlerUseLeft);
			put(bh, ttPlus, stInt, stPointer, HandlerUseRight);
            put(bh, ttPlus, stChar, stPointer, HandlerUseRight);                   
			
			put(bh, ttMinus, stPointer, stPointer, HandlerDistanceBetweenPointers);

            put(bh, ttAssign, stInt, stInt, HandlerUseLeft);
            put(bh, ttAssign, stChar, stChar, HandlerUseLeft);
            put(bh, ttAssign, stFloat, stFloat, HandlerUseLeft);
            put(bh, ttAssign, stFloat, stInt, HandlerUseLeftWithCast);
            put(bh, ttAssign, stFloat, stChar, HandlerUseLeftWithCast);
            put(bh, ttAssign, stInt, stFloat, HandlerUseLeftWithCast);
            put(bh, ttAssign, stChar, stFloat, HandlerUseLeftWithCast);
            put(bh, ttAssign, stInt, stChar, HandlerUseLeft);
            put(bh, ttAssign, stChar, stInt, HandlerUseLeft);
            put(bh, ttAssign, stPointer, stPointer, HandlerUseLeft);
            put(bh, ttAssign, stFunctionPointer, stFunctionPointer, HandlerUseLeftWithTypeCheck);
            put(bh, ttAssign, stFunctionPointer, stFunctionType, HandlerAssignFunctionToPtr);

            put(bh, ttAssign, stStruct, stStruct, HandlerUseLeftWithTypeCheck);

            std::map<UnaryOpOnType, UnaryOpHandler> &uh = AvailableUnaryOpsAndHandlers;
            put(uh, ttAsterisk, stPointer, HandlerDereference);
            put(uh, ttAsterisk, stFunctionPointer, HandlerDereference);
            put(uh, ttInc, stInt, HandlerLeaveSame);
            put(uh, ttInc, stChar, HandlerLeaveSame);
            put(uh, ttInc, stPointer, HandlerLeaveSame);
            put(uh, ttDecr, stInt, HandlerLeaveSame);
            put(uh, ttDecr, stChar, HandlerLeaveSame);
            put(uh, ttDecr, stPointer, HandlerLeaveSame);
            put(uh, ttPlus, stInt, HandlerLeaveSame);
            put(uh, ttPlus, stChar, HandlerLeaveSame);
            put(uh, ttPlus, stFloat, HandlerLeaveSame);
            put(uh, ttMinus, stInt, HandlerLeaveSame);
            put(uh, ttMinus, stChar, HandlerLeaveSame);
            put(uh, ttMinus, stFloat, HandlerLeaveSame);
            put(uh, ttNot, stInt, HandlerLeaveSame);
            put(uh, ttNot, stChar, HandlerLeaveSame);
            put(uh, ttNot, stFloat, HandlerLeaveSame);
            put(uh, ttComplement, stInt, HandlerLeaveSame);
            put(uh, ttComplement, stChar, HandlerLeaveSame);
            put(uh, ttBAnd, stStruct, HandlerGetAddress);
            put(uh, ttBAnd, stInt, HandlerGetAddress);
            put(uh, ttBAnd, stChar, HandlerGetAddress);
            put(uh, ttBAnd, stFloat, HandlerGetAddress);
            put(uh, ttBAnd, stPointer, HandlerGetAddress);
            put(uh, ttBAnd, stFunctionPointer, HandlerGetAddress);
            put(uh, ttBAnd, stFunctionType, HandlerGetFunctionAddress);

            //put(uh, ttBAnd, stStruct, HandlerGetAddress);


            //UnaryOps.insert(ttInc);
            //UnaryOps.insert(ttDecr);
            //UnaryOps.insert(ttAsterisk);
            //UnaryOps.insert(ttBAnd);
            //UnaryOps.insert(ttPlus);
            //UnaryOps.insert(ttMinus);
            //UnaryOps.insert(ttNot);
            //UnaryOps.insert(ttComplement);
    	}
	}
}
